import {createToken as createTokenOrg, Lexer} from "chevrotain";

// A little mini DSL for easier lexer definition.
const fragments = {};
const f = fragments;

function FRAGMENT(name, def) {
  fragments[name] = typeof def === "string" ? def : def.source;
}

function makePattern(strings, ...args) {
  let combined = "";
  for (let i = 0; i < strings.length; i++) {
    combined += strings[i];
    if (i < args.length) {
      let pattern = args[i];
      // if a TokenType was passed
      if (args[i].PATTERN) {
        pattern = args[i].PATTERN;
      }
      const patternSource =
        typeof pattern === "string" ? pattern : pattern.source;
      // By wrapping in a RegExp (none) capturing group
      // We enabled the safe usage of qualifiers and assertions.
      combined += `(?:${patternSource})`;
    }
  }
  return new RegExp(combined);
}

const tokensArray = [];
const tokensDictionary = {};

function createToken(options) {
  const newTokenType = createTokenOrg(options);
  tokensArray.push(newTokenType);
  tokensDictionary[options.name] = newTokenType;
  return newTokenType;
}

// TODO: would there be perf benefits to add repetition (+) to newline def?
const Newline = createToken({name: "Newline", pattern: /\n|\r\n/});
const Whitespace = createToken({
  name: "Whitespace",
  pattern: /[ \t]+/,
  group: Lexer.SKIPPED
});
createToken({
  name: "Comment",
  pattern: /#(?:[^\n\r]|\r(?!\n))*/
});
createToken({name: "KeyValSep", pattern: "="});
createToken({name: "Dot", pattern: "."});
const IKey = createToken({name: "IKey", pattern: Lexer.NA});
const IQuotedKey = createToken({
  name: "IQuotedKey",
  pattern: Lexer.NA,
  categories: [IKey]
});
const IUnquotedKey = createToken({
  name: "IUnquotedKey",
  pattern: Lexer.NA,
  categories: [IKey]
});
const IString = createToken({name: "IString", pattern: Lexer.NA});
// TODO: comment on unicode complements and \uFFFF range
FRAGMENT(
  "basic_unescaped",
  /[\u0020-\u0021]|[\u0023-\u005B]|[\u005D-\u007E]|[\u0080-\uFFFF]/
);
FRAGMENT("escaped", /\\(?:[btnfr"\\]|u[0-9a-fA-F]{4}(?:[0-9a-fA-F]{4})?)/);
FRAGMENT("basic_char", makePattern`${f.basic_unescaped}|${f.escaped}`);
FRAGMENT(
  "ML_BASIC_UNESCAPED",
  // TODO: comment on unicode complements and \uFFFF range
  // SPEC Deviation: included backslash (5C)
  //      See: https://github.com/toml-lang/toml/pull/590
  /[\u0020-\u005B]|[\u005D-\u007E]|[\u0080-\uFFFF]/
);
FRAGMENT("ML_BASIC_CHAR", makePattern`${f.ML_BASIC_UNESCAPED}|${f.escaped}`);
FRAGMENT(
  "ML_BASIC_BODY",
  makePattern`(?:${f.ML_BASIC_CHAR}|${Newline}|\\\\${Whitespace}?${Newline})*`
);
createToken({
  name: "BasicMultiLineString",
  pattern: makePattern`"""${f.ML_BASIC_BODY}"""`,
  categories: [IString]
});
createToken({
  name: "BasicString",
  pattern: makePattern`"${f.basic_char}*"`,
  categories: [IString, IQuotedKey]
});
FRAGMENT(
  "LITERAL_CHAR",
  /[\u0009]|[\u0020-\u0026]|[\u0028-\u007E]|[\u0080-\uFFFF]/
);
FRAGMENT("ML_LITERAL_CHAR", /[\u0009]|[\u0020-\u007E]|[\u0080-\uFFFF]/);
createToken({
  name: "LiteralMultiLineString",
  pattern: makePattern`'''(?:${f.ML_LITERAL_CHAR}|${Newline})*'''`,
  categories: [IString]
});
createToken({
  name: "LiteralString",
  pattern: makePattern`'${f.LITERAL_CHAR}*'`,
  categories: [IString, IQuotedKey]
});
const IBoolean = createToken({
  name: "IBoolean",
  pattern: Lexer.NA,
  categories: [IUnquotedKey]
});
const True = createToken({
  name: "True",
  pattern: /true/,
  categories: [IBoolean]
});
const False = createToken({
  name: "False",
  pattern: /false/,
  categories: [IBoolean]
});
const IDateTime = createToken({
  name: "IDateTime",
  pattern: Lexer.NA
});
createToken({
  name: 'ResourceName',
  pattern: /[a-zA-Z0-9_\-]+@\d+/
})
createToken({
  name: 'LVarCurly',
  pattern: /{{/,
})
createToken({
  name: 'RVarCurly',
  pattern: /}}/,
})
FRAGMENT("date_fullyear", /\d{4}/);
FRAGMENT("date_month", /\d{2}/);
FRAGMENT("date_mday", /\d{2}/);
FRAGMENT("time_delim", /[tT ]/);
FRAGMENT("time_hour", /\d{2}/);
FRAGMENT("time_minute", /\d{2}/);
FRAGMENT("time_second", /\d{2}/);
FRAGMENT("time_secfrac", /.\d+/);
FRAGMENT("time_numoffset", makePattern`[+-]${f.time_hour}:${f.time_minute}`);
FRAGMENT("time_offset", makePattern`[zZ]|${f.time_numoffset}`);
FRAGMENT(
  "partial_time",
  makePattern`${f.time_hour}:${f.time_minute}:${f.time_second}${
    f.time_secfrac
  }?`
);
FRAGMENT(
  "full_date",
  makePattern`${f.date_fullyear}-${f.date_month}-${f.date_mday}`
);
FRAGMENT("full_time", makePattern`${f.partial_time}${f.time_offset}`);
createToken({
  name: "OffsetDateTime",
  pattern: makePattern`${f.full_date}${f.time_delim}${f.full_time}`,
  categories: [IDateTime]
});
createToken({
  name: "LocalDateTime",
  pattern: makePattern`${f.full_date}${f.time_delim}${f.partial_time}`,
  categories: [IDateTime]
});
createToken({
  name: "LocalDate",
  pattern: makePattern`${f.full_date}`,
  categories: [IDateTime]
});
createToken({
  name: "LocalTime",
  pattern: makePattern`${f.partial_time}`,
  categories: [IDateTime]
});
const IFloat = createToken({
  name: "IFloat",
  pattern: Lexer.NA
});

const decimalIntPatern = /[+-]?(?:0|[1-9](?:_?\d)*)/;
FRAGMENT("float_int_part", decimalIntPatern);
FRAGMENT("decimal_point", /\./);
FRAGMENT("zero_prefixable_int", /\d(?:_?\d)*/);
FRAGMENT("exp", makePattern`[eE]${f.float_int_part}`);
FRAGMENT("frac", makePattern`${f.decimal_point}${f.zero_prefixable_int}`);
createToken({
  name: "Float",
  pattern: makePattern`${f.float_int_part}(?:${f.exp}|${f.frac}${f.exp}?)`,
  categories: [IFloat]
});
createToken({
  name: "SpecialFloat",
  pattern: /[+-](?:inf|nan)/,
  categories: [IFloat]
});
const IInteger = createToken({
  name: "IInteger",
  pattern: Lexer.NA
});
const DecimalInt = createToken({
  name: "DecimalInt",
  pattern: decimalIntPatern,
  // Not that DecimalInt is both an IInteger **and** an IUnquotedKey
  categories: [IInteger, IUnquotedKey]
});
createToken({
  name: "HexInt",
  pattern: /0x[0-9A-F](?:_?[0-9A-F])*/,
  categories: [IInteger]
});
createToken({
  name: "OctInt",
  pattern: /0o[0-7](?:_?[0-7])*/,
  categories: [IInteger]
});
createToken({
  name: "BinInt",
  pattern: /0b[0-1](?:_?[0-1])*/,
  categories: [IInteger]
});
createToken({
  name: "LSquare",
  pattern: "["
});
createToken({
  name: "RSquare",
  pattern: "]"
});
createToken({
  name: "Comma",
  pattern: ","
});
createToken({
  name: "LCurly",
  pattern: "{"
});
createToken({
  name: "RCurly",
  pattern: "}"
});

const UnquotedKey = createToken({
  name: "UnquotedKey",
  pattern: /[A-Za-z0-9_-]+/,
  categories: [IUnquotedKey]
});
const possibleUnquotedKeysPrefixes = [True, False, DecimalInt];
possibleUnquotedKeysPrefixes.forEach(tokType => {
  tokType.LONGER_ALT = UnquotedKey;
});

export {
  tokensArray,
  tokensDictionary
};
